use std::sync::Arc;

use cookie::{time::Duration, Cookie};

use ntex::{
    http::Response,
    web::{
        types::{Json, State},
        Responder,
    },
};

use reqwest::Client;

use crate::{
    config,
    errors::CustomError,
    models::user::{AccessToken, GithubUserInfo, Login},
    AppState,
};

// receive income code, get access_token, save user data into database
pub async fn github_login(
    code: Json<Login>,
    state: State<Arc<AppState>>,
) -> Result<impl Responder, CustomError> {
    let code = &code.code;

    // http client
    let client = Client::new();

    // get access_toke
    // let Accept set as json, and let github api return json data to us
    let access_token = client
        .post(format!(
            "https://github.com/login/oauth/access_token?client_id={}&client_secret={}&code={code}",
            config::CLIENT_ID,
            config::CLIENT_SECRET
        ))
        .header("Accept", "application/json")
        .send()
        .await;

    let access_token = match access_token {
        Ok(r) => match r.json::<AccessToken>().await {
            Ok(r) => r.access_token,
            Err(_) => {
                return {
                    Err(CustomError::AuthFailed("code is unuseable (maybe out of date), please redo login with github account".into()))
                }
            }
        },

        Err(_) => {
            return Err(CustomError::InternalServerError(
                "can not get access_token, please try again".into(),
            ));
        }
    };

    let user_info = client
        .get("https://api.github.com/user")
        .bearer_auth(access_token.clone())
        .send()
        .await;

    let user_info = match user_info {
        Ok(r) => r.json::<GithubUserInfo>().await.unwrap(),

        Err(_) => {
            return Err(CustomError::InternalServerError(
                "can not get github user info, please retry".into(),
            ));
        }
    };

    let mut cookie = Cookie::new("ACCESS_TOKEN", access_token);

    cookie.set_path("/");
    cookie.set_max_page(Duration::days(69)); // 69 days outdate TODO:
    cookie.set_http_only(true);

    let db_pool = &state.db_pool;

    sqlx::query!(
      "INSERT INTO users (id, name, avatar_url) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET name=$2, avatar_url = $3",
      user_info.id as i32,
      user_info.login
      user_info.avatar_url
    )
    .execute(db_pool)
    .await?;

    let mut response = Response::Ok().body(format!("Hi, {}", user_info.login));

    let _ = response.add_cookie(&cookie);

    Ok(response)
}
